﻿using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Linq;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using System.Collections.Generic;

namespace CommonWFLibrary
{
    /// <summary>
    /// Run a workflow - but do it and idle the current workflow while waiting for it to
    /// complete.
    /// </summary>
	public partial class RunLongWorkflow: LongRunningActivityBase
	{
		public RunLongWorkflow()
		{
			InitializeComponent();
		}

        /// <summary>
        /// The type of the wf that we are supposed to be starting
        /// </summary>
        public Type WFType
        {
            get { return (Type)GetValue(WFTypeProperty); }
            set { SetValue(WFTypeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for WFType.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty WFTypeProperty =
            DependencyProperty.Register("WFType", typeof(Type), typeof(RunLongWorkflow), new PropertyMetadata(null));

        /// <summary>
        /// Arguments to be passed to the workflow
        /// </summary>
        public Dictionary<string, object> WFArguments
        {
            get { return (Dictionary<string, object>)GetValue(WFArgumentsProperty); }
            set { SetValue(WFArgumentsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for WFArguments.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty WFArgumentsProperty =
            DependencyProperty.Register("WFArguments", typeof(Dictionary<string, object>), typeof(RunLongWorkflow), new PropertyMetadata(null));

        /// <summary>
        /// The output arguments from the wf that we ran.
        /// </summary>
        public Dictionary<string, object> WFResults
        {
            get { return (Dictionary<string, object>)GetValue(WFResultsProperty); }
            set { SetValue(WFResultsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for WFResults.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty WFResultsProperty =
            DependencyProperty.Register("WFResults", typeof(Dictionary<string, object>), typeof(RunLongWorkflow), new PropertyMetadata(null));

        /// <summary>
        /// Args that we pass to the wf
        /// </summary>
        [Serializable]
        public class Args
        {
            public Type _wfType;
            public Dictionary<string, object> _wfArgs;
        }

        [LongRunningGatherArguments]
        public Args Gather()
        {
            Args a = new Args();
            a._wfType = WFType;
            a._wfArgs = WFArguments;
            return a;
        }

        [Serializable]
        public class RAContext
        {
            public WorkflowInstance _instance;
        }

        [Serializable]
        public class Results
        {
            public Dictionary<string, object> _wfResults;
        }

        /// <summary>
        /// Save the results coming back from the WF to the local WF instance variables.
        /// </summary>
        /// <param name="r"></param>
        [LongRunningDistributeArguments]
        public void SetWFOutputs(Results r)
        {
            WFResults = r._wfResults;
        }

        /// <summary>
        /// Helper class that monitors the WF engine for this particular workflow to finish.
        /// </summary>
        private class WFFinishContext
        {
            private WorkflowInstance _me = null;
            private LongRunningContext _context;

            public WFFinishContext(LongRunningContext context, WorkflowInstance me)
            {
                _me = me;
                RegisterCallbacks(context.Runtime);
                _context = context;
            }

            /// <summary>
            /// Register callbacks on the runtime.
            /// </summary>
            /// <param name="runtime"></param>
            private void RegisterCallbacks(WorkflowRuntime runtime)
            {
                runtime.WorkflowAborted += new EventHandler<WorkflowEventArgs>(runtime_WorkflowAborted);
                runtime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(runtime_WorkflowCompleted);
                runtime.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(runtime_WorkflowTerminated);
            }

            /// <summary>
            /// Unregister the callback so we don't have to stay around any longer.
            /// </summary>
            /// <param name="runtime"></param>
            private void UnregisterCallbacks(WorkflowRuntime runtime)
            {
                runtime.WorkflowAborted -= new EventHandler<WorkflowEventArgs>(runtime_WorkflowAborted);
                runtime.WorkflowCompleted -= new EventHandler<WorkflowCompletedEventArgs>(runtime_WorkflowCompleted);
                runtime.WorkflowTerminated -= new EventHandler<WorkflowTerminatedEventArgs>(runtime_WorkflowTerminated);
            }

            /// <summary>
            /// WF is terminated. Deal with it!
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            void runtime_WorkflowTerminated(object sender, WorkflowTerminatedEventArgs e)
            {
                UnregisterCallbacks(_context.Runtime);
                Results r = new Results();
                _context.Complete(r);
            }

            /// <summary>
            /// WF has been completed. Deal with it!
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            void runtime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
            {
                UnregisterCallbacks(_context.Runtime);
                Results r = new Results();
                r._wfResults = e.OutputParameters;
                _context.Complete(r);
            }

            /// <summary>
            /// WF has been aborted. Deal with it.
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            void runtime_WorkflowAborted(object sender, WorkflowEventArgs e)
            {
                UnregisterCallbacks(_context.Runtime);
                Results r = new Results();
                _context.Complete(r);
            }
        }

        /// <summary>
        /// Start up the wf activity!
        /// </summary>
        /// <param name="context"></param>
        /// <param name="start"></param>
        /// <param name="lastTime"></param>
        /// <returns></returns>
        [LongRunningMethodStarter]
        static public RAContext RunExternalWF(LongRunningContext context, Args start, RAContext lastTime)
        {
            ///
            /// Ok. Now, if the work flow hasn't been started, then we should start it up now!
            /// Otherwise, assume persistance correctly took care of it, and do nothing future.
            /// 

            if (lastTime == null)
            {
                lastTime = new RAContext();

                lastTime._instance = context.Runtime.CreateWorkflow(start._wfType, start._wfArgs);
                lastTime._instance.Start();
            }

            ///
            /// First, make sure that we are attached to the WF runtime. We need to listen for things to "finish up"
            /// 

            WFFinishContext wfmonitor = new WFFinishContext(context, lastTime._instance);

            ///
            /// Done - let 'em know we are continuing on.
            /// 

            return lastTime;
        }
	}
}
